// **********************************************************************
// 
// <copyright>
// 
//  BBN Technologies
//  10 Moulton Street
//  Cambridge, MA 02138
//  (617) 873-8000
// 
//  Copyright (C) BBNT Solutions LLC. All rights reserved.
// 
// </copyright>
// **********************************************************************
// 
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/event/ProjectionSupport.java,v $
// $RCSfile: ProjectionSupport.java,v $
// $Revision: 1.10 $
// $Date: 2009/02/05 18:46:11 $
// $Author: dietrick $
// 
// **********************************************************************

package com.bbn.openmap.event;

import java.util.ListIterator;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.bbn.openmap.proj.Projection;

/**
 * This is a utility class that can be used by beans that need support for
 * handling ProjectionListeners and firing ProjectionEvents. You can use an
 * instance of this class as a member field of your bean and delegate work to
 * it.
 */
public class ProjectionSupport extends ListenerSupport<ProjectionListener> {

    static Logger logger = Logger.getLogger("com.bbn.openmap.event.ProjectionSupport");
    private static final long serialVersionUID = 1L;
    protected ProjectionChangeNotifier pcNotifier;
    protected boolean useNotifier;

    /**
     * Construct a ProjectionSupport.
     */
    public ProjectionSupport(boolean useNotifier) {
        this(null, useNotifier);
    }

    /**
     * Construct a ProjectionSupport.
     * 
     * @param aSource source Object
     */
    public ProjectionSupport(Object aSource, boolean useNotifier) {
        super(aSource);
        this.useNotifier = useNotifier;
    }

    /**
     * Send a center event to all registered listeners.
     * 
     * @param proj Projection
     */
    public void fireProjectionChanged(Projection proj) {
        if (proj == null || isEmpty())
            return; // no event or no listeners

        if (useNotifier && pcNotifier == null) {
            pcNotifier = new ProjectionChangeNotifier();
            pcNotifier.start();
        }

        ProjectionEvent event = new ProjectionEvent(getSource(), proj);

        if (pcNotifier != null) {
            pcNotifier.fireProjectionEvent(event);
        } else {
            for (ProjectionListener listener : this) {
                listener.projectionChanged(event);
            }
        }
    }

    /**
     * Call when getting rid of the ProjectionSupport, it kills the
     * ProjectionSupport thread. (from CJS)
     */
    public void dispose() {
        super.clear();
        if (pcNotifier != null) {
            pcNotifier.setTerminated(true);
            pcNotifier.fireProjectionEvent(null);
            pcNotifier.interrupt();
            pcNotifier = null;
        }
    }

    /**
     * A thread that disperses the projection event, instead of letting the
     * Swing thread do it. A new one is created for every projection change, so
     * the current ProjectionEvent object is getting delivered with it.
     */
    protected class ProjectionChangeNotifier extends Thread {

        private final Object lock = new Object();

        /* current projection event */
        protected ProjectionEvent projEvent;

        /* next projection event */
        protected ProjectionEvent nextEvent;

        /* a flag to know if we are terminated. */
        protected boolean terminated = false;

        public ProjectionChangeNotifier() {
            setName("ProjectionSupportThread " + getName());
        }

        public boolean isTerminated() {
            return terminated;
        }

        public void setTerminated(boolean terminated) {
            this.terminated = terminated;
        }

        protected boolean isEventInProgress() {
            // synchronized(lock){
            return projEvent != null;
            // }
        }

        public void fireProjectionEvent(ProjectionEvent event) {
            synchronized (lock) {
                nextEvent = event;
                lock.notifyAll(); // wakes up thread if sleeping
            }
        }

        public void run() {
            while (!terminated) { // run while parent mapbean exists
                synchronized (lock) {
                    if (nextEvent != null) {
                        projEvent = nextEvent;
                        nextEvent = null;
                    }
                }

                if (projEvent != null && !isEmpty()) {

                    // Instead of going top of map to bottom, go bottom to top:

                    /*
                     * Use this try/catch to deal with any problems getting
                     * clone of listeners, in case listener list is being
                     * changed while clone is being made, etc.
                     */
                    try {

                        ListIterator<ProjectionListener> li = ProjectionSupport.this.listIterator();
                        while (li.hasPrevious()) {
                            ProjectionListener listener = li.previous();

                            if (nextEvent != null) {
                                break; // new event has been posted, bail out
                            }

                            /*
                             * Use this try/catch to eliminate problems from
                             * individual layers - just blow them off.
                             */
                            try {
                                synchronized (lock) {
                                    listener.projectionChanged(projEvent); 
                                }
                            } catch (Exception e) {
                                if (logger.isLoggable(Level.FINE)) {
                                    logger.info("ProjectionListener not handling projection well: "
                                            + listener.getClass().getName() + " : "
                                            + e.getClass().getName() + " : " + e.getMessage());
                                    e.printStackTrace();
                                }
                            }
                        }

                    } catch (Exception e) {
                        logger.fine("caught exception: " + e.getClass().getName() + " : "
                                + e.getMessage());
                    }

                    // notification is complete
                    synchronized (lock) {
                        projEvent = null;
                    }
                } else { // there is no event
                    // just wait until we are awakened for the next event
                    try {
                        synchronized (lock) {
                            while (nextEvent == null) {
                                lock.wait();                                                                
                            }
                        }
                    } catch (InterruptedException x) {
                        // do nothing, just reenter loop
                    }
                }
            }

            logger.fine("Projection notifier thread " + getName() + " done running");
        }
    }

}